"""
五子棋游戏WebSocket处理器

处理五子棋游戏的实时通信，包括游戏状态更新、落子通知等。
"""

import json
import logging
from typing import Dict, List, Set, Any, Optional
from datetime import datetime

from fastapi import WebSocket, WebSocketDisconnect, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.db.session import get_db
from app.modules.gomoku.config import settings

# 配置日志
logger = logging.getLogger(__name__)


class ConnectionManager:
    """
    WebSocket连接管理器
    
    管理游戏的WebSocket连接，实现广播和消息分发。
    """
    
    def __init__(self):
        # 按游戏ID组织连接
        self.active_connections: Dict[int, List[WebSocket]] = {}
        # 跟踪每个连接的用户ID
        self.connection_user: Dict[WebSocket, int] = {}
        # 观战者连接
        self.spectator_connections: Dict[int, List[WebSocket]] = {}
    
    async def connect(self, websocket: WebSocket, game_id: int, user_id: int, is_spectator: bool = False):
        """建立连接"""
        await websocket.accept()
        
        # 根据角色添加到不同的连接池
        if is_spectator:
            if game_id not in self.spectator_connections:
                self.spectator_connections[game_id] = []
            self.spectator_connections[game_id].append(websocket)
        else:
            if game_id not in self.active_connections:
                self.active_connections[game_id] = []
            self.active_connections[game_id].append(websocket)
            self.connection_user[websocket] = user_id
        
        # 发送连接确认消息
        await websocket.send_json({
            "type": "connection_established",
            "game_id": game_id,
            "user_id": user_id,
            "timestamp": datetime.utcnow().isoformat(),
            "is_spectator": is_spectator
        })
        
        logger.info(f"WebSocket连接已建立: game={game_id}, user={user_id}, spectator={is_spectator}")
    
    async def disconnect(self, websocket: WebSocket, game_id: int):
        """断开连接"""
        # 从所有可能的连接池中移除
        if game_id in self.active_connections:
            if websocket in self.active_connections[game_id]:
                self.active_connections[game_id].remove(websocket)
            if not self.active_connections[game_id]:
                del self.active_connections[game_id]
        
        if game_id in self.spectator_connections:
            if websocket in self.spectator_connections[game_id]:
                self.spectator_connections[game_id].remove(websocket)
            if not self.spectator_connections[game_id]:
                del self.spectator_connections[game_id]
        
        if websocket in self.connection_user:
            del self.connection_user[websocket]
        
        logger.info(f"WebSocket连接已断开: game={game_id}")
    
    async def broadcast(self, game_id: int, message: Dict[str, Any]):
        """向游戏的所有连接广播消息"""
        # 向玩家广播
        if game_id in self.active_connections:
            for connection in self.active_connections[game_id]:
                try:
                    await connection.send_json(message)
                except Exception as e:
                    logger.error(f"广播消息失败: {str(e)}")
        
        # 向观战者广播
        if game_id in self.spectator_connections:
            for connection in self.spectator_connections[game_id]:
                try:
                    await connection.send_json(message)
                except Exception as e:
                    logger.error(f"广播消息给观战者失败: {str(e)}")


# 创建连接管理器实例
manager = ConnectionManager()


async def handle_game_connection(
    websocket: WebSocket,
    game_id: int,
    token: str,
    is_spectator: bool = False,
    db: AsyncSession = Depends(get_db)
):
    """
    处理游戏WebSocket连接
    
    接受连接，处理消息，管理断开。
    
    Args:
        websocket: WebSocket连接
        game_id: 游戏ID
        token: 认证令牌
        is_spectator: 是否为观战者
        db: 数据库会话
    """
    # 验证用户身份（简化版）
    from app.core.security import decode_access_token
    user_id = decode_access_token(token)
    
    if not user_id:
        await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
        return
    
    # 验证游戏
    from app.modules.gomoku.services import game_service
    game = await game_service.get_game(db, game_id=game_id)
    
    if not game:
        await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
        return
    
    # 非观战者必须是游戏参与者
    if not is_spectator and game.player1_id != user_id and game.player2_id != user_id:
        await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
        return
    
    try:
        # 建立连接
        await manager.connect(websocket, game_id, user_id, is_spectator)
        
        # 通知其他玩家有新连接
        await manager.broadcast(game_id, {
            "type": "user_connected",
            "game_id": game_id,
            "user_id": user_id,
            "is_spectator": is_spectator,
            "timestamp": datetime.utcnow().isoformat()
        })
        
        # 消息处理循环
        while True:
            # 等待消息
            message_json = await websocket.receive_text()
            message = json.loads(message_json)
            message_type = message.get("type", "")
            
            # 处理不同类型的消息
            if message_type == "ping":
                # 心跳消息
                await websocket.send_json({"type": "pong", "timestamp": datetime.utcnow().isoformat()})
            
            elif message_type == "chat":
                # 聊天消息
                if "content" in message:
                    await manager.broadcast(game_id, {
                        "type": "chat",
                        "game_id": game_id,
                        "user_id": user_id,
                        "content": message["content"],
                        "timestamp": datetime.utcnow().isoformat()
                    })
            
            elif message_type == "move" and not is_spectator:
                # 落子消息
                if "x" in message and "y" in message:
                    from app.modules.gomoku.schemas.game import GameMoveCreate
                    
                    # 创建落子数据
                    move_data = GameMoveCreate(x=message["x"], y=message["y"])
                    
                    # 执行落子
                    game, game_move, is_win = await game_service.make_move(
                        db, game_id=game_id, user_id=user_id, move=move_data
                    )
                    
                    if game and game_move:
                        # 广播落子结果
                        response = {
                            "type": "move_made",
                            "game_id": game_id,
                            "user_id": user_id,
                            "x": game_move.x,
                            "y": game_move.y,
                            "color": game_move.color.value,
                            "move_number": game_move.move_number,
                            "timestamp": datetime.utcnow().isoformat(),
                        }
                        
                        # 如果获胜，添加获胜信息
                        if is_win:
                            response["result"] = "win"
                            response["winner_id"] = user_id
                        elif game.status == "completed" and game.is_draw:
                            response["result"] = "draw"
                        else:
                            response["result"] = None
                            response["next_turn"] = game.current_turn.value
                        
                        await manager.broadcast(game_id, response)
            
            elif message_type == "resign" and not is_spectator:
                # 认输消息
                game = await game_service.resign_game(db, game_id=game_id, user_id=user_id)
                
                if game:
                    # 广播认输结果
                    await manager.broadcast(game_id, {
                        "type": "game_resigned",
                        "game_id": game_id,
                        "user_id": user_id,
                        "winner_id": game.winner_id,
                        "timestamp": datetime.utcnow().isoformat()
                    })
    
    except WebSocketDisconnect:
        # 处理连接断开
        await manager.disconnect(websocket, game_id)
        
        # 通知其他玩家有连接断开
        await manager.broadcast(game_id, {
            "type": "user_disconnected",
            "game_id": game_id,
            "user_id": user_id,
            "is_spectator": is_spectator,
            "timestamp": datetime.utcnow().isoformat()
        })
    
    except Exception as e:
        # 处理其他异常
        logger.error(f"WebSocket处理错误: {str(e)}")
        await manager.disconnect(websocket, game_id)


# 辅助函数
async def decode_access_token(token: str) -> Optional[int]:
    """
    解码访问令牌获取用户ID
    
    Args:
        token: JWT访问令牌
        
    Returns:
        用户ID，如果令牌无效则返回None
    """
    try:
        from jose import jwt, JWTError
        from app.core.config import settings
        from app.core.security import ALGORITHM
        
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])
        user_id = int(payload.get("sub"))
        return user_id
    except (JWTError, ValueError):
        return None 